﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="TypeExtensions.cs" company="CodePlex">
//   The MIT License
//   Copyright (c) 2013 Julien Blin
//   
//   Permission is hereby granted, free of charge, to any person obtaining a copy
//   of this software and associated documentation files (the "Software"), to deal
//   in the Software without restriction, including without limitation the rights
//   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//   copies of the Software, and to permit persons to whom the Software is
//   furnished to do so, subject to the following conditions:
//   
//   The above copyright notice and this permission notice shall be included in
//   all copies or substantial portions of the Software.
//   
//   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//   THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------

namespace UCodeFirst
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Reflection;

    using Umbraco.Core;

    /// <summary>
    /// Holds extension methods for <see cref="Type"/>.
    /// </summary>
    public static class TypeExtensions
    {
        /// <summary>
        /// Creates an instance of type <paramref name="type"/> using the default constructor.
        /// </summary>
        /// <param name="type">
        /// The type to instantiate.
        /// </param>
        /// <returns>
        /// The <see cref="object"/> instantiated.
        /// </returns>
        public static object CreateInstance(this Type type)
        {
            try
            {
                return Activator.CreateInstance(type);
            }
            catch (Exception ex)
            {
                throw new UCodeFirstException(Resources.ImpossibleToInstantiateType.InvariantFormat(type), ex);
            }
        }

        /// <summary>
        /// Creates an instance of type <paramref name="type"/> using the default constructor,
        /// and cast it to <typeparamref name="T"/>.
        /// </summary>
        /// <param name="type">
        /// The type to instantiate.
        /// </param>
        /// <typeparam name="T">
        /// The type to cast and return. Can be the same or a related type.
        /// </typeparam>
        /// <returns>
        /// The <see cref="object"/> instantiated.
        /// </returns>
        public static T CreateInstance<T>(this Type type)
        {
            var instance = type.CreateInstance();
            if (!(instance is T))
            {
                throw new UCodeFirstException(Resources.UnableToCastObject.InvariantFormat(instance, typeof(T)));
            }

            return (T)instance;
        }

        /// <summary>
        /// Indicates whether the type has a custom attribute of type <typeparamref name="T"/>.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="inherit">
        /// true to search the inheritance chain, false otherwise.
        /// </param>
        /// <typeparam name="T">
        /// The custom attribute type to look for
        /// </typeparam>
        /// <returns>
        /// True if the type has at least one custom attribute of type <typeparamref name="T"/>, false otherwise.
        /// </returns>
        public static bool HasAttr<T>(this Type type, bool inherit = false)
        {
            return type.GetCustomAttributes(typeof(T), inherit).Any();
        }

        /// <summary>
        /// Returns the first custom attribute on the type, or null if none found.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="inherit">
        /// true to search the inheritance chain, false otherwise.
        /// </param>
        /// <typeparam name="T">
        /// The custom attribute type to look for
        /// </typeparam>
        /// <returns>
        /// The custom attribute, or null if none found.
        /// </returns>
        public static T GetAttr<T>(this Type type, bool inherit = false)
        {
            return (T)type.GetCustomAttributes(typeof(T), inherit).FirstOrDefault();
        }

        /// <summary>
        /// Returns the custom attributes on the type.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <param name="inherit">
        /// true to search the inheritance chain, false otherwise.
        /// </param>
        /// <typeparam name="T">
        /// The custom attribute type to look for
        /// </typeparam>
        /// <returns>
        /// The custom attributes, or empty enumeration if not found.
        /// </returns>
        public static IEnumerable<T> GetAttrs<T>(this Type type, bool inherit = false)
        {
            return type.GetCustomAttributes(typeof(T), inherit).Cast<T>();
        }

        /// <summary>
        /// Indicates whether the member has a custom attribute of type <typeparamref name="T"/>.
        /// </summary>
        /// <param name="memberInfo">
        /// The member.
        /// </param>
        /// <param name="inherit">
        /// true to search the inheritance chain, false otherwise.
        /// </param>
        /// <typeparam name="T">
        /// The custom attribute type to look for
        /// </typeparam>
        /// <returns>
        /// True if the member has at least one custom attribute of type <typeparamref name="T"/>, false otherwise.
        /// </returns>
        public static bool HasAttr<T>(this MemberInfo memberInfo, bool inherit = false)
        {
            return memberInfo.GetCustomAttributes(typeof(T), inherit).Any();
        }

        /// <summary>
        /// Returns the first custom attribute on the member, or null if none found.
        /// </summary>
        /// <param name="memberInfo">
        /// The member.
        /// </param>
        /// <param name="inherit">
        /// true to search the inheritance chain, false otherwise.
        /// </param>
        /// <typeparam name="T">
        /// The custom attribute type to look for
        /// </typeparam>
        /// <returns>
        /// The custom attribute, or null if none found.
        /// </returns>
        public static T GetAttr<T>(this MemberInfo memberInfo, bool inherit = false)
        {
            return (T)memberInfo.GetCustomAttributes(typeof(T), inherit).FirstOrDefault();
        }

        /// <summary>
        /// Order the types by their deepness order in the class hierarchy.
        /// The less a class has base types, the higher rank it has.
        /// </summary>
        /// <param name="values">
        /// The values.
        /// </param>
        /// <returns>
        /// The types ordered by hierarchy.
        /// </returns>
        public static IEnumerable<Type> OrderByClassInheritanceLevel(this IEnumerable<Type> values)
        {
            return values.OrderBy(x =>
            {
                Type currentType = x;
                var deepLevel = 0;
                while (currentType.BaseType != null)
                {
                    ++deepLevel;
                    currentType = currentType.BaseType;
                }

                return deepLevel;
            });
        }

        /// <summary>
        /// Returns the names of the <see cref="Enum"/> values, using <see cref="EnumNameAttribute"/> when present.
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// The list of names.
        /// </returns>
        public static IEnumerable<string> GetEnumNamesUsingEnumAttribute(this Type value)
        {
            return Enum.GetNames(value)
                       .Select(x => value.GetMember(x).First())
                       .Select(x => x.HasAttr<EnumNameAttribute>() ? x.GetAttr<EnumNameAttribute>().Name : x.Name);
        }

        /// <summary>
        /// Returns the names associated with the <see cref="Enum"/> values, using <see cref="EnumNameAttribute"/> when present.
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <returns>
        /// The list of names.
        /// </returns>
        public static IDictionary<string, object> GetEnumNamesAndValuesUsingEnumAttribute(this Type value)
        {
            return Enum.GetValues(value)
                       .Cast<object>()
                       .Distinct()
                       .ToDictionary(x =>
                                {
                                    var memberInfo = value.GetMember(Enum.GetName(value, x)).First();
                                    return memberInfo.HasAttr<EnumNameAttribute>() ? memberInfo.GetAttr<EnumNameAttribute>().Name : memberInfo.Name;
                                });
        }

        /// <summary>
        /// Determine if a type is <see cref="Nullable"/>.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <returns>
        /// True if the type is <see cref="Nullable"/>, false otherwise.
        /// </returns>
        public static bool IsNullable(this Type type)
        {
            return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
        }

        /// <summary>
        /// Returns the type of IEnumerable handled by the type, or null if not found.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <returns>The IEnumerable type.</returns>
        public static Type GetGenericEnumerableType(this Type type)
        {
            Type enumerableType = null;
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            {
                enumerableType = type;
            }

            if (enumerableType == null)
            {
                enumerableType = type.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEnumerable<>));
            }

            if (enumerableType == null)
            {
                return null;
            }

            return enumerableType.GetGenericArguments().First();
        }

        /// <summary>
        /// Get all the base types recursively.
        /// </summary>
        /// <param name="type">
        /// The type.
        /// </param>
        /// <returns>
        /// The base types.
        /// </returns>
        public static IEnumerable<Type> GetAllBaseTypes(this Type type)
        {
            var current = type;
            while (current.BaseType != null)
            {
                yield return current.BaseType;
                current = current.BaseType;
            }
        }
    }
}
